package monitor

import (
	"smart-flow/server/config"
	"smart-flow/server/goal"
	"smart-flow/server/job"
	"smart-flow/server/logger"
	"sync"
	"time"

	"go.uber.org/zap"

	cmap "github.com/orcaman/concurrent-map"
)

var (
	containerOnce sync.Once
	containersIns *Containers
)

type Containers struct {
	containers cmap.ConcurrentMap
}

func ContainersInstance() *Containers {
	containerOnce.Do(func() {
		containersIns = &Containers{
			containers: cmap.New(),
		}
	})
	return containersIns
}

func (c *Containers) GetEnableTargets(task *job.FlowJob) ([]*goal.Target, []*goal.Target, int, int) {
	if task.Isp == config.IspMobile {
		return c.getEnableTargets4P(task)
	} else {
		return c.getEnableTargets4A(task)
	}
}

func (c *Containers) getEnableTargets4A(task *job.FlowJob) ([]*goal.Target, []*goal.Target, int, int) {
	containers := GetContainersByIsp(task.Isp)
	if containers == nil {
		return nil, nil, 0, 0
	}
	var (
		targetsV4, targetsV6       []*goal.Target
		limitTotalV4, limitTotalV6 int
	)
	for _, co := range containers {
		if co.MaxDownTotal < 1000*1000 {
			logger.Logger.Warn("[业务] 容器下行总带宽限制设置过低", zap.String("containerId:", co.MachineId), zap.String("containerIp:", co.MonitorIp))
			continue
		}
		availableDown := int64(uint64(co.MaxDownTotal*config.AppConfig().Dy.Bar/100) - co.DownTotal)
		logger.Logger.Info("[业务] 容器下行总带宽检测", zap.Int64("maxBandwidth:", co.MaxDownTotal), zap.Uint64("usedBandwidth:", co.DownTotal), zap.Int64("availBandwidth:", availableDown),
			zap.String("containerId:", co.MachineId), zap.String("containerIp:", co.MonitorIp))
		if availableDown <= 0 {
			logger.Logger.Info("[业务] 容器下行总带宽已达设置上限", zap.String("containerId:", co.MachineId), zap.String("containerIp:", co.MonitorIp))
			continue
		}
		limitV4 := getTargetLimitV4(task, co)
		if limitV4 > 0 {
			limitTotalV4 += limitV4
			targetsV4 = append(targetsV4, &goal.Target{
				TargetCtx: goal.TargetCtx{
					MachineId:  co.MachineId,
					Isp:        co.Isp,
					Ip:         co.MonitorIp,
					InfoUrl:    co.InfoUrl,
					DownUrl:    co.DownUrl,
					ProveV6Url: co.ProveV6Url,
					LimitV4:    limitV4,
					LimitV6:    0,
				},
			})
		}
		limitV6 := getTargetLimitV6(task, co)
		if limitV6 > 0 {
			limitTotalV6 += limitV6
			targetsV6 = append(targetsV6, &goal.Target{
				TargetCtx: goal.TargetCtx{
					MachineId:  co.MachineId,
					Isp:        co.Isp,
					Ip:         co.MonitorIp,
					InfoUrl:    co.InfoUrl,
					DownUrl:    co.DownUrl,
					ProveV6Url: co.ProveV6Url,
					LimitV4:    0,
					LimitV6:    limitV6,
				},
			})
		}
	}
	return targetsV4, targetsV6, limitTotalV4, limitTotalV6
}

func (c *Containers) getEnableTargets4P(task *job.FlowJob) ([]*goal.Target, []*goal.Target, int, int) {
	containers := GetContainersByIsp(task.Isp)
	if containers == nil {
		return nil, nil, 0, 0
	}
	pusherLimit := config.AppConfig().Dy.PusherLimit
	if pusherLimit > 0 && GetOnlineCountByIsp(task.Isp) > pusherLimit {
		return nil, nil, 0, 0
	}
	var (
		targetsV4, targetsV6       []*goal.Target
		limitTotalV4, limitTotalV6 int
	)
	for _, co := range containers {
		if task.V4Enabled == 1 {
			limitTotalV4 += config.MaxLimit
			targetsV4 = append(targetsV4, &goal.Target{
				TargetCtx: goal.TargetCtx{
					MachineId:  co.MachineId,
					Isp:        co.Isp,
					Ip:         co.MonitorIp,
					InfoUrl:    co.InfoUrl,
					DownUrl:    co.DownUrl,
					ProveV6Url: co.ProveV6Url,
					LimitV4:    config.MaxLimit,
					LimitV6:    0,
				},
			})
		}
		if task.V6Enabled == 1 {
			limitTotalV6 += config.MaxLimit
			targetsV6 = append(targetsV6, &goal.Target{
				TargetCtx: goal.TargetCtx{
					MachineId:  co.MachineId,
					Isp:        co.Isp,
					Ip:         co.MonitorIp,
					InfoUrl:    co.InfoUrl,
					DownUrl:    co.DownUrl,
					ProveV6Url: co.ProveV6Url,
					LimitV4:    0,
					LimitV6:    config.MaxLimit,
				},
			})
		}
	}
	return targetsV4, targetsV6, limitTotalV4, limitTotalV6
}

func TargetLimitedV4(target *goal.Target) bool {
	return target.CountV4.Add(1) > int32(target.LimitV4)
}

func TargetLimitedV6(target *goal.Target) bool {
	return target.CountV6.Add(1) > int32(target.LimitV6)
}

func getTargetLimitV4(task *job.FlowJob, container *ContainerData) int {
	if task.V4Enabled == 0 || container.MaxDownTotalV4 < 1000*1000 {
		return 0
	}
	availableDown := int64(uint64(container.MaxDownTotalV4*config.AppConfig().Dy.Bar/100) - container.DownTotal)
	if availableDown <= 0 {
		logger.Logger.Info("[业务] 容器V4下行带宽已达设置上限", zap.String("containerId:", container.MachineId), zap.String("containerIp:", container.MonitorIp))
		return 0
	}
	limit := max(min(int(availableDown/int64(min(task.RunRate*1000, config.TaskDefaultRate))), config.MaxLimit), 1)
	logger.Logger.Info("[业务] 容器V4下行带宽检测", zap.Int64("maxBandwidthV4:", container.MaxDownTotalV4), zap.Uint64("usedBandwidthV4:", container.DownTotal), zap.Int64("availBandwidthV4:", availableDown),
		zap.Int("limitV4", limit), zap.String("containerId:", container.MachineId), zap.String("containerIp:", container.MonitorIp))
	return limit
}

func getTargetLimitV6(task *job.FlowJob, container *ContainerData) int {
	if task.V6Enabled == 0 || container.MaxDownTotalV6 < 1000*1000 {
		return 0
	}
	availableDown := int64(uint64(container.MaxDownTotalV6*config.AppConfig().Dy.Bar/100) - container.DownTotal)
	if availableDown <= 0 {
		logger.Logger.Info("[业务] 容器V6下行带宽已达设置上限", zap.String("containerId:", container.MachineId), zap.String("containerIp:", container.MonitorIp))
		return 0
	}
	limit := max(min(int(availableDown/int64(min(task.RunRate*1000, config.TaskDefaultRate))), config.MaxLimit), 1)
	logger.Logger.Info("[业务] 容器V6下行带宽检测", zap.Int64("maxBandwidthV6:", container.MaxDownTotalV6), zap.Uint64("usedBandwidthV6:", container.DownTotal), zap.Int64("availBandwidthV6:", availableDown),
		zap.Int("limitV6", limit), zap.String("containerId:", container.MachineId), zap.String("containerIp:", container.MonitorIp))
	return limit
}

// nolint: unused
func getTargetLimit4P() int {
	now := time.Now()
	year, month, day := now.Date()
	cutoff18 := time.Date(year, month, day, 18, 0, 0, 0, now.Location())
	cutoff2210 := time.Date(year, month, day, 22, 10, 0, 0, now.Location())
	if now.Before(cutoff18) {
		return int(now.Sub(time.Date(year, month, day, 0, 0, 0, 0, now.Location())).Minutes())
	}
	if now.After(cutoff18) && now.Before(cutoff2210) {
		return 1000
	}
	return 0
}
